"""
    @File        : classics_fight.py
    @Author      : 狡猾的皮球
    @Date        : 2020/7/9 21:20
    @QQ          : 871245007
    @Description :
        经典回合制战斗系统，类似于梦幻西游的战斗系统
        战斗单位属性：
            基本属性：
                等级：最低0级，最高200级
                气血：气血分为当前气血和气血上限
                魔法：魔法分为当前魔法和魔法上限
                法防：法术防御力
                伤害：物理攻击力
                防御：物理防御力
                速度：出手速度
                灵力：法术攻击力
                暴击：出现暴击的几率，只有装备或者buff才能增加暴击率，初始暴击率为5%
            修炼属性：
                攻击修炼：提升物理伤害 level*2%+10*level
                法术修炼：提升法术伤害 level*2%+5*level
                防御修炼：减少物理伤害 level*2%+5*level
                法防修炼：减少法术伤害 level*2%+5*level
            技能属性（类型）：
                单体物理攻击
                群体物理攻击
                单体法术攻击
                群体法术攻击
                单体恢复（加血或者加蓝）
                群体恢复（加血或者加蓝）
                单体增益（增加攻击力、防御力、暴击率等等）
                群体增益（增加攻击力、防御力、暴击率等等）
                单体减益（降低攻击力、防御力、暴击率，封印、睡眠、中毒敌人）
                群体减益（降低攻击力、防御力、暴击率，封印、睡眠、中毒敌人）
                被动技能（增加攻击力、防御力、暴击率、攻击带毒、攻击连击、法术连击等等）

        战斗系统运行流程：
            1.初始化战斗双方战斗对象
            2.初始化战斗脚本数据（可能会在指定回合触发剧情等等）
            3.玩家给我方人员下达操作指令（攻击、法术、道具）
            4.敌人自动下达操作指令（特殊战斗可能按照战斗脚本数据下达指令，一般战斗由AI做出决策）
            5.检测回合开始时是否触发脚本
            6.计算本回合的战斗结果，产出本回合的战斗数据，例如:
                [
                    {"source":"李逍遥","cmd":"普通攻击","target":"千叶禅师","damage":500,"是否暴击":"是"},
                    {"source":"千叶禅师","cmd":"普通攻击","target":"李逍遥","damage":100,"是否暴击":"否"},
                    {"source":"李忆如","cmd":"道具","target":"李逍遥","item":"金创药","damage":300},
                ]
            7.根据计算的战斗结果播放战斗动画
            8.检测回合结束时是否触发脚本
            9.是否战斗结束？
                *是，结束战斗，结算奖励
                *否，重复3、4、5、6、7、8、9步骤

        其他：
            最多支持5vs5的战斗

        开发进度：
            * 场景，已完成
            * 人物状态控制，已完成
            * 人物高亮，已完成
            * 鼠标选择人物之后高亮，已完成
            * 操作菜单
            * 显示人物气血和魔法值
"""
import json
import math
import random
from typing import List

import pygame

from code.engine.animation import Animation
from code.engine.gui import ButtonEx
from code.engine.sprite import Sprite, draw_src_text
from code.game_global import g


class Fighter:
    """
    战斗者类
    """
    STEP = 1  # 移动的时候，每一帧移动的距离（像素）
    ENUM_IDLE = 0  # 闲置状态
    ENUM_ATK = 1  # 攻击状态
    ENUM_BE_ATK = 2  # 被攻击状态
    ENUM_MAGIC = 3  # 施法状态
    ENUM_DEAD = 4  # 死亡状态

    def __init__(self, **kwargs):
        # 战斗管理器引用
        self.fight_mgr = None
        # 战斗基本属性
        self.level = kwargs["level"]  # 等级
        self.hp = kwargs["hp"]  # 生命值
        self.mp = kwargs["mp"]  # 魔法值
        self.magic_def = kwargs["magic_def"]  # 法术防御力
        self.atk = kwargs["atk"]  # 伤害（物理攻击力）
        self.defense = kwargs["defense"]  # 防御力
        self.agi = kwargs["agi"]  # 速度
        self.magic = kwargs["magic"]  # 灵力
        self.cri = kwargs["cri"]  # 暴击率，单位%
        # 修炼属性
        self.atk_practice = kwargs["atk_practice"]  # 攻击修炼
        self.magic_practice = kwargs["magic_practice"]  # 法术修炼
        self.def_practice = kwargs["def_practice"]  # 防御修炼
        self.magic_def_practice = kwargs["magic_def_practice"]  # 法防修炼
        # 其他属性
        self.name = kwargs["name"]  # 姓名
        self.is_enemy = kwargs["is_enemy"]  # 是否为敌人
        self.position = kwargs["position"]  # 几号位
        # 渲染属性
        self.id = kwargs["id"]
        # 以下坐标都是中心点坐标
        # 初始位置坐标
        self.raw_x = None
        self.raw_y = None
        # 实时位置的坐标
        self.x = None
        self.y = None
        # 起点坐标
        self.x0 = 0
        self.y0 = 0
        # 终点坐标
        self.xt = None
        self.yt = None
        self.n = 0  # 位移进度[0,1]
        self.k = 0  # 位移比例（每帧的位移比例）
        self.moving = False  # 位移开关
        self.ani_idle = None  # 闲置动画
        self.ani_attack = None  # 攻击动画
        self.ani_be_attacked = None  # 被攻击动画
        self.ani_magic = None  # 施法动画
        # 人物动画状态
        self.state = Fighter.ENUM_IDLE  # 0闲置状态 1攻击状态 2被攻击状态 3施法状态 4死亡状态
        self.attack_target = None  # 普通攻击目标

        self.load()

    def load(self):
        """
        加载资源
        """
        # 加载配置文件
        with open(f'./resource/PicLib/fighter/{self.id}/attack.json', 'r', encoding='utf8') as file:
            cfg = json.load(file)
        # 创建闲置动画
        dw = cfg['idle_ani']['cell_w']
        dh = cfg['idle_ani']['cell_h']
        time = cfg['idle_ani']['time']
        frame_range = cfg['idle_ani']['frame_range']
        img_idle, img_idle_light = g.res_mgr.get_two_surface(f"./resource/PicLib/fighter/{self.id}/idle.png")
        self.ani_idle = Animation(self.x, self.y, img_idle, dw, dh, time, True, frame_range, fps=g.fps)
        self.ani_idle.img_light = img_idle_light
        # 创建攻击动画
        dw = cfg['attack_ani']['cell_w']
        dh = cfg['attack_ani']['cell_h']
        time = cfg['attack_ani']['time']
        frame_range = cfg['attack_ani']['frame_range']
        sound_frame = cfg['attack_ani']['sound_frame']
        extra_frame = cfg['attack_ani']['extra_frame']
        attack_frame = cfg['attack_ani']['attack_frame']
        move_frame = cfg['attack_ani']['move_frame']
        img_atk = g.res_mgr.get_surface(f"./resource/PicLib/fighter/{self.id}/attack.png")
        frame_count = int(time / (frame_range[1] - frame_range[0] + 1) / (1000 / g.fps))
        self.ani_attack = Animation(
            self.x,
            self.y,
            img_atk,
            dw,
            dh,
            time,
            False,
            frame_range,
            fps=g.fps,
            frame_callback=self.attack_cb(sound_frame, attack_frame, extra_frame, move_frame,frame_count),
            done_callback=None
        )
        # 创建被攻击动画
        dw = cfg['be_attacked_ani']['cell_w']
        dh = cfg['be_attacked_ani']['cell_h']
        time = cfg['be_attacked_ani']['time']
        frame_range = cfg['be_attacked_ani']['frame_range']
        img_be_atk = g.res_mgr.get_surface(f"./resource/PicLib/fighter/{self.id}/be_attacked.png")
        self.ani_be_attacked = Animation(
            self.x,
            self.y,
            img_be_atk,
            dw,
            dh,
            time,
            False,
            frame_range,
            fps=g.fps
        )

    def attack_cb(self, sound_frame, attack_frame, extra_frame, move_frame, frame_count):
        """
        攻击回调
        """

        def cb(frame):
            for sf in sound_frame:
                if sf[1] == frame:
                    # 播放音效
                    g.audio_player.play_sound(sf[0])
                    break
            if frame == attack_frame[0]:
                # 敌人改为受击状态
                self.attack_target.state = Fighter.ENUM_BE_ATK

                # 攻击帧，出现伤害
                # t = 'attack'
                # if self.cri:
                #     t = 'cri'
                # g.fight_mgr.damage_list.append(DamageAnimation(t, self.damage, self.other_x, self.other_y))
            elif frame == attack_frame[1]:
                # 敌人状态还原
                self.attack_target.state = Fighter.ENUM_IDLE

            # if extra_frame is not None and frame == extra_frame:
            #     # 有额外动画，触发而外动画
            #     self.attack_extra_ani.reset()
            #     self.ani_list.append(self.attack_extra_ani)

            # 触发移动事件
            if move_frame is not None:
                if frame == move_frame[0][0]:
                    # 向敌人移动
                    total_frame = move_frame[0][1] - move_frame[0][0] + 1
                    self.start_move(self.attack_target.x + 50, self.attack_target.y, total_frame * frame_count)
                if frame == move_frame[1][0]:
                    # 回到自己的位置
                    total_frame = move_frame[1][1] - move_frame[1][0] + 1
                    self.start_move(self.raw_x, self.raw_y, total_frame * frame_count)

        return cb

    def set_xy(self, x, y):
        self.x = x
        self.y = y
        self.ani_idle.x = x
        self.ani_idle.y = y
        self.ani_attack.x = x
        self.ani_attack.y = y
        self.ani_be_attacked.x = x
        self.ani_be_attacked.y = y

    def logic(self):
        self.move()
        if self.state == Fighter.ENUM_IDLE:
            self.ani_idle.update()
        elif self.state == Fighter.ENUM_ATK:
            self.ani_attack.update()
        elif self.state == Fighter.ENUM_BE_ATK:
            offset_x = random.randint(1, 7)
            if self.is_enemy:
                offset_x *= -1
            self.ani_be_attacked.x = self.x + offset_x
            self.ani_be_attacked.update()

    def render(self, target):
        if self.state == Fighter.ENUM_IDLE:
            self.ani_idle.draw(target)
        elif self.state == Fighter.ENUM_ATK:
            self.ani_attack.draw(target)
        elif self.state == Fighter.ENUM_BE_ATK:
            self.ani_be_attacked.draw(target)

        self.render_hp_mp(target)

    def start_move(self, x, y, frame_amount):
        """
        设置终点，开启位移
        frame_amount是主循环次数，不是动画帧数，动画帧*动画frame_amount才等于frame_amount
        x,y是终点
        """
        self.x0 = self.x
        self.y0 = self.y
        self.xt = x
        self.yt = y
        # length = math.sqrt((self.xt - self.x) ** 2 - (self.yt - self.y) ** 2)
        self.n = 0
        self.k = 1 / frame_amount
        self.moving = True

    def move(self):
        """
        实时计算当前坐标
        """
        if not self.moving:
            return
        self.n += self.k
        if self.n >= 1:
            self.n = 1
            self.moving = False
        self.x = self.n * (self.xt - self.x0) + self.x0
        self.y = self.n * (self.yt - self.y0) + self.y0
        self.set_xy(self.x, self.y)

    def render_hp_mp(self, target):
        """
        绘制生命值和魔法值
        """
        Sprite.draw_text(target, self.x, self.y, f"{self.hp[0]}/{self.hp[1]}", g.fnt_talk, (255, 0, 0))
        Sprite.draw_text(target, self.x, self.y + 20, f"{self.mp[0]}/{self.mp[1]}", g.fnt_talk, (0, 0, 255))

    def mouse_move(self, x, y):
        """
        鼠标移动事件
        """
        if self.state != self.ENUM_IDLE:
            return False
        # 只有人物处于idle状态才能高亮,鼠标是否点中
        dx, dy = int(x - (self.x - self.ani_idle.dw / 2)), int(y - (self.y - self.ani_idle.dh / 2))
        return self.ani_idle.collide(dx, dy)


class FightMenu:
    """
    战斗操作菜单
    """

    def __init__(self, x, y):
        self.img_ani_menu = pygame.image.load("./resource/PicLib/all_sys/fight_menu_new.png").convert_alpha()
        self.ani_menu = Animation(x, y, self.img_ani_menu, 142, 143, 600, False, [0, 8], fps=g.fps)
        self.state = 0
        self.visible = True
        self.x = x
        self.y = y
        btn_fnt = pygame.font.SysFont('microsoftyaheimicrosoftyaheiui', 16, bold=True)

        # 7,28
        img_left_normal = pygame.image.load("./resource/PicLib/all_sys/fight_menu_left_1.png").convert_alpha()
        img_left_move = pygame.image.load("./resource/PicLib/all_sys/fight_menu_left_3.png").convert_alpha()
        img_left_down = pygame.image.load("./resource/PicLib/all_sys/fight_menu_left_2.png").convert_alpha()
        self.btn_left = ButtonEx(self.x + 7, self.y + 28, "仙术", btn_fnt, img_left_normal, img_left_move, img_left_down,
                                 cb=self.reset, rgb=(255, 255, 255), rgb2=(0, 0, 255))

        # 95,28
        img_right_normal = pygame.image.load("./resource/PicLib/all_sys/fight_menu_right_1.png").convert_alpha()
        img_right_move = pygame.image.load("./resource/PicLib/all_sys/fight_menu_right_3.png").convert_alpha()
        img_right_down = pygame.image.load("./resource/PicLib/all_sys/fight_menu_right_2.png").convert_alpha()
        self.btn_right = ButtonEx(self.x + 95, self.y + 28, "道具", btn_fnt, img_right_normal, img_right_move,
                                  img_right_down, cb=self.reset, rgb=(255, 255, 255), rgb2=(0, 0, 255))

        # 27,7
        img_up_normal = pygame.image.load("./resource/PicLib/all_sys/fight_menu_up_1.png").convert_alpha()
        img_up_move = pygame.image.load("./resource/PicLib/all_sys/fight_menu_up_3.png").convert_alpha()
        img_up_down = pygame.image.load("./resource/PicLib/all_sys/fight_menu_up_2.png").convert_alpha()
        self.btn_up = ButtonEx(self.x + 27, self.y + 7, "待定", btn_fnt, img_up_normal, img_up_move, img_up_down,
                               cb=self.reset, rgb=(255, 255, 255), rgb2=(0, 0, 255))

        # 27,96
        img_down_normal = pygame.image.load("./resource/PicLib/all_sys/fight_menu_down_1.png").convert_alpha()
        img_down_move = pygame.image.load("./resource/PicLib/all_sys/fight_menu_down_3.png").convert_alpha()
        img_down_down = pygame.image.load("./resource/PicLib/all_sys/fight_menu_down_2.png").convert_alpha()
        self.btn_down = ButtonEx(self.x + 27, self.y + 96, "待定", btn_fnt, img_down_normal, img_down_move, img_down_down,
                                 cb=self.reset, rgb=(255, 255, 255), rgb2=(0, 0, 255))

        # 49,49
        img_mid_normal = pygame.image.load("./resource/PicLib/all_sys/fight_menu_mid_1.png").convert_alpha()
        img_mid_move = pygame.image.load("./resource/PicLib/all_sys/fight_menu_mid_3.png").convert_alpha()
        img_mid_down = pygame.image.load("./resource/PicLib/all_sys/fight_menu_mid_2.png").convert_alpha()
        self.btn_mid = ButtonEx(self.x + 49, self.y + 49, "攻击", btn_fnt, img_mid_normal, img_mid_move, img_mid_down,
                                cb=self.reset, rgb=(255, 255, 255), rgb2=(0, 0, 255))

    def logic(self):
        if not self.visible:
            return
        self.ani_menu.update()
        if self.ani_menu.least_once:
            self.ani_menu.pause = True

    def render(self, target):
        if not self.visible:
            return
        self.ani_menu.draw_src(target, self.x, self.y)
        if self.ani_menu.pause:
            self.btn_left.draw(target)
            self.btn_right.draw(target)
            self.btn_up.draw(target)
            self.btn_down.draw(target)
            self.btn_mid.draw(target)

    def mouse_down(self, x, y, pressed):
        if not self.visible:
            return
        if self.ani_menu.pause:
            self.btn_left.mouse_down(x, y)
            self.btn_right.mouse_down(x, y)
            self.btn_up.mouse_down(x, y)
            self.btn_down.mouse_down(x, y)
            self.btn_mid.mouse_down(x, y)

    def mouse_move(self, x, y):
        if not self.visible:
            return
        if self.ani_menu.pause:
            self.btn_left.mouse_move(x, y)
            self.btn_right.mouse_move(x, y)
            self.btn_up.mouse_move(x, y)
            self.btn_down.mouse_move(x, y)
            self.btn_mid.mouse_move(x, y)

    def mouse_up(self):
        if not self.visible:
            return
        if self.ani_menu.pause:
            self.btn_left.mouse_up()
            self.btn_right.mouse_up()
            self.btn_up.mouse_up()
            self.btn_down.mouse_up()
            self.btn_mid.mouse_up()

    def reset(self):
        self.ani_menu.reset()


class FightManager:
    """
    战斗管理器
    """
    # 友军位置
    TEAMMATE_POSITION = [
        [355 - 40, 342 - 60],
        [452 - 40, 288 - 60],
        [376 - 40, 427 - 60],
        [470 - 40, 388 - 60],
        [575 - 40, 324 - 60]
    ]
    # 敌人位置
    ENEMY_POSITION = [
        [295 - 40, 207 - 60],
        [198 - 40, 262 - 60],
        [274 - 40, 133 - 60],
        [183 - 40, 180 - 60],
        [83 - 40, 226 - 60]
    ]

    def __init__(self):
        """
        初始化战斗管理器
        """
        # 敌人列表
        self.enemy_list = []
        # 我方列表
        self.teammate_list = []
        # 两者列表
        self.fighter_list = []
        # 回合数
        self.round = 1
        # 是否打开战斗系统
        self.switch = False
        # 战斗背景地图
        self.img_bg = None
        # 战斗菜单动画
        self.fight_menu = FightMenu(0, 480 - 143)

    def start(self, teammate_list: List[Fighter], enemy_list: List[Fighter], map_id):
        """
        开始战斗
        """
        self.teammate_list = teammate_list
        self.enemy_list = enemy_list
        self.fighter_list = self.teammate_list + self.enemy_list
        self.img_bg = pygame.image.load(f"./resource/PicLib/all_fight/{map_id}.jpg")
        self.switch = True
        # 初始化人物位置
        for fighter in self.fighter_list:
            fighter.fight_mgr = self
            if fighter.is_enemy:
                x, y = self.ENEMY_POSITION[fighter.position]
            else:
                x, y = self.TEAMMATE_POSITION[fighter.position]
            fighter.raw_x = x
            fighter.raw_y = y
            fighter.set_xy(x, y)
        # TODO:测试位移
        t1 = self.teammate_list[0]
        t2 = self.teammate_list[1]
        t3 = self.teammate_list[2]
        t4 = self.teammate_list[3]
        t5 = self.teammate_list[4]

        e1 = self.enemy_list[0]
        e2 = self.enemy_list[1]
        e3 = self.enemy_list[2]
        e4 = self.enemy_list[3]
        e5 = self.enemy_list[4]

        t1.attack_target = e2
        t1.state = Fighter.ENUM_ATK

        t2.attack_target = e3
        t2.state = Fighter.ENUM_ATK

        # t3.attack_target = e4
        # t3.state = Fighter.ENUM_ATK

        t4.attack_target = e5
        t4.state = Fighter.ENUM_ATK

        e1.attack_target = t5
        e1.state = Fighter.ENUM_ATK

        # 让t1高亮
        t3.ani_idle.sw_light = True
        e5.ani_idle.sw_light = True

    def logic(self):
        self.fight_menu.logic()
        for fighter in self.fighter_list:
            fighter.logic()
        self.fighter_list.sort(key=lambda obj: obj.y)

    def render(self, surface):
        if not self.switch:
            return
        # 画背景
        Sprite.blit(surface, self.img_bg, 0, 0)
        # 画战斗者
        for fighter in self.fighter_list:
            fighter.render(surface)
        self.fight_menu.render(surface)

    def mouse_down(self, x, y, pressed):
        self.fight_menu.mouse_down(x, y, pressed)

    def mouse_move(self, x, y):
        self.fight_menu.mouse_move(x, y)
        focus = None
        for fighter in self.fighter_list[::-1]:
            if fighter.mouse_move(x, y):
                fighter.ani_idle.sw_light = True
                focus = fighter
                break
        for fighter in self.fighter_list:
            if fighter != focus:
                fighter.ani_idle.sw_light = False
        if not focus:
            return False

        return True

    def mouse_up(self, x, y, pressed):
        self.fight_menu.mouse_up()
